package com.sf.fy.uav.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sf.fy.uav.common.enums.*;
import com.sf.fy.uav.common.request.operate.CouponRequest;
import com.sf.fy.uav.common.request.operate.QueryCouponRequest;
import com.sf.fy.uav.common.response.operational.CouponResponse;
import com.sf.fy.uav.common.response.operational.EffectiveCouponResponse;
import com.sf.fy.uav.common.response.operational.EffectiveCouponRsResponse;
import com.sf.fy.uav.common.utils.BeanUtil;
import com.sf.fy.uav.common.utils.RedisPrefixKey;
import com.sf.fy.uav.controller.CouponActivityController;
import com.sf.fy.uav.entity.Coupon;
import com.sf.fy.uav.entity.UserCoupon;
import com.sf.fy.uav.mapper.CouponMapper;
import com.sf.fy.uav.mapper.UserCouponMapper;
import com.sf.fy.uav.service.CouponService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestParam;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author lixiang
 * @since 2023-09-12
 */
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements CouponService {
    final CouponMapper couponMapper;

    @Autowired
    private CouponActivityController couponActivityController;

    @Value("${lock.try_time:3}")
    private long lockTryTime;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private UserCouponMapper userCouponMapper;

    public CouponServiceImpl(CouponMapper couponMapper) {
        this.couponMapper = couponMapper;
    }

    private static final Integer DEL = 0;
    private static final Byte RADIO_TYPE_TRUE = 0;
    private static final Byte RADIO_TYPE_FALSE = 1;

    private static final Integer CONST = 0;

    private static final Logger logger = LoggerFactory.getLogger(CouponServiceImpl.class);

    @Override
    public List<CouponResponse> queryCoupons(QueryCouponRequest queryCouponRequest) {
        QueryWrapper<Coupon> couponQueryWrapper = new QueryWrapper<>();
        getQueryWrapper(couponQueryWrapper, queryCouponRequest.getCouponName(), queryCouponRequest.getStatus());
        couponQueryWrapper.orderByDesc("update_time");
        Page<Coupon> couponPage = couponMapper.selectPage(new Page<>(queryCouponRequest.getCurrent(), queryCouponRequest.getSize()), couponQueryWrapper);
        List<CouponResponse> couponResponseList = BeanUtil.copyProperties(couponPage.getRecords(), CouponResponse.class);
        for (CouponResponse couponResponse : couponResponseList) {
            couponResponse.setStatusName(CouponStatusEnum.getDesc(couponResponse.getStatus()));
            couponResponse.setCouponTypeName(CouponTypeEnum.getDesc(couponResponse.getCouponType()));
            couponResponse.setDesignatedUser(CouponUserTypeEnum.getDesc(couponResponse.getDesignatedUserType()));
        }
        return couponResponseList;
    }

    @Override
    public Long getTotal(String couponName, Byte status) {
        QueryWrapper<Coupon> couponQueryWrapper = new QueryWrapper<>();
        getQueryWrapper(couponQueryWrapper, couponName, status);
        return couponMapper.selectCount(couponQueryWrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Coupon createCoupon(CouponRequest couponRequest) {
        if (StringUtils.isBlank(couponRequest.getCouponName())) {
            throw new RuntimeException("优惠券名称不能为空!");
        }
        if (StringUtils.isBlank(couponRequest.getOperator())) {
            throw new RuntimeException("操作人不能为空!");
        }
        if (ObjectUtils.isEmpty(couponRequest.getDiscount())) {
            throw new RuntimeException("折扣数不能为空!");
        }
        if (ObjectUtils.isEmpty(couponRequest.getCouponType())) {
            throw new RuntimeException("优惠券类型不能为空!");
        }
        if (ObjectUtils.isEmpty(couponRequest.getThreshold())) {
            throw new RuntimeException("使用门槛限制不能为空!");
        }
        if (ObjectUtils.isEmpty(couponRequest.getTotalQuantity())) {
            throw new RuntimeException("发行总量不能为空!");
        }
        Integer effectiveDays = couponRequest.getEffectiveDays();
        if (ObjectUtils.isEmpty(effectiveDays)) {
            if (ObjectUtils.isEmpty(couponRequest.getEffectiveTime())) {
                throw new RuntimeException("优惠券生效时间不能为空!");
            }
            if (ObjectUtils.isEmpty(couponRequest.getFailureTime())) {
                throw new RuntimeException("优惠券失效时间不能为空!");
            }
            if (couponRequest.getEffectiveTime().getTime() < System.currentTimeMillis()) {
                throw new RuntimeException("开始时间不能小于当前时间!");
            }
            if (couponRequest.getEffectiveTime().equals(couponRequest.getFailureTime()) || couponRequest.getEffectiveTime().getTime() > couponRequest.getFailureTime().getTime()) {
                throw new RuntimeException("开始时间不能大于等于结束时间!");
            }
        } else {
            couponRequest.setStatus(CouponStatusEnum.UNDER_WAY.getValue());
        }

        if (ObjectUtils.isEmpty(couponRequest.getClaimRestriction())) {
            throw new RuntimeException("领取次数限制不能为空!");
        }
        if (ObjectUtils.isEmpty(couponRequest.getDesignatedUserType())) {
            throw new RuntimeException("指定用户不能为空!");
        }
        if (RADIO_TYPE_FALSE.equals(couponRequest.getThreshold()) && ObjectUtils.isEmpty(couponRequest.getThresholdAmount()) && couponRequest.getThresholdAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new RuntimeException("请输入门槛金额!");
        }
        if (RADIO_TYPE_TRUE.equals(couponRequest.getThreshold()) && ObjectUtils.isNotEmpty(couponRequest.getThresholdAmount()) && couponRequest.getThresholdAmount().compareTo(BigDecimal.ZERO) > 0) {
            throw new RuntimeException("请清空门槛金额!");
        }
        if (RADIO_TYPE_FALSE.equals(couponRequest.getClaimRestriction()) && ObjectUtils.isEmpty(couponRequest.getClaimNum()) && couponRequest.getClaimNum() <= CONST) {
            throw new RuntimeException("请输入限领次数!");
        }
        if (RADIO_TYPE_TRUE.equals(couponRequest.getClaimRestriction()) && ObjectUtils.isNotEmpty(couponRequest.getClaimNum()) && couponRequest.getClaimNum() > CONST) {
            throw new RuntimeException("请清空限领次数!");
        }

        if (ObjectUtils.isNotEmpty(couponRequest.getThresholdAmount())) {
            thresholdCheck(couponRequest.getThresholdAmount());
        }
        discountCheck(couponRequest.getDiscount());
        totalQuantityCheck(couponRequest.getTotalQuantity());
        Date now = new Date();
        Coupon coupon = BeanUtil.copyProperties(couponRequest, Coupon.class);
        coupon.setUpdateTime(now);
        coupon.setCreateTime(now);
        coupon.setDel(false);

        if (RADIO_TYPE_TRUE.equals(couponRequest.getThreshold())) {
            coupon.setDescription("无门槛,打" + couponRequest.getDiscount() + "折");
        } else if (RADIO_TYPE_FALSE.equals(couponRequest.getThreshold())) {
            coupon.setDescription("满" + couponRequest.getThresholdAmount() + "元," + "打" + couponRequest.getDiscount() + "折");
        }
        couponMapper.insert(coupon);
        return coupon;
    }

    @Override
    public Coupon getNewerCoupon() {
        QueryWrapper<Coupon> couponQueryWrapper = new QueryWrapper<>();
        Coupon coupon = couponMapper.selectOne(couponQueryWrapper);
        return coupon;
    }

    @Override
    public CouponResponse queryCouponById(String id) {
        return BeanUtil.copyProperties(couponMapper.selectById(id), CouponResponse.class);
    }

    @Override
    public UserCoupon selectById(String id) {
        UserCoupon userCoupon = userCouponMapper.selectById(id);
        if (ObjectUtils.isEmpty(userCoupon)) {
            throw new RuntimeException("优惠券不存在!");
        }
        Integer userCouponStatus = userCoupon.getStatus();
        if (!UserCouponStatusEnum.NOT_USED.getValue().equals(userCouponStatus)) {
            throw new RuntimeException("优惠券已使用或已过期!");
        }
        return userCoupon;
    }

    @Override
    public EffectiveCouponRsResponse getEffectiveCoupons(@RequestParam Integer page, @RequestParam Integer size) {
        EffectiveCouponRsResponse effectiveCouponRsResponse = new EffectiveCouponRsResponse();
        QueryWrapper<Coupon> couponQueryWrapper = new QueryWrapper<>();
        couponQueryWrapper.lambda().and(e -> e.isNotNull(Coupon::getEffectiveDays).or().gt(Coupon::getFailureTime, new Date())).apply("total_quantity - already_rec_num > {0}", 0).notIn(Coupon::getStatus, Arrays.asList(CouponStatusEnum.ENDED.getValue(), CouponStatusEnum.LOST_EFFICACY.getValue())).eq(Coupon::getDel, false);

        Long cnt = couponMapper.selectCount(couponQueryWrapper);
        Page<Coupon> couponPage = couponMapper.selectPage(new Page<>(page, size), couponQueryWrapper);
        List<Coupon> coupons = couponPage.getRecords();
        coupons = coupons.stream().sorted(Comparator.comparing(Coupon::getUpdateTime).reversed()).toList();
        List<EffectiveCouponResponse> effectiveCouponResponses = new ArrayList<>();
        coupons.forEach(coupon -> {
            EffectiveCouponResponse effectiveCouponResponse = BeanUtil.copyProperties(coupon, EffectiveCouponResponse.class);
            effectiveCouponResponse.setCouponId(coupon.getId());
            effectiveCouponResponse.setCouponTypeName(CouponTypeEnum.getDesc(coupon.getCouponType()));
            effectiveCouponResponse.setStock(coupon.getTotalQuantity() - coupon.getAlreadyRecNum());
            effectiveCouponResponses.add(effectiveCouponResponse);
        });
        effectiveCouponRsResponse.setEffectiveCouponResponses(effectiveCouponResponses);
        effectiveCouponRsResponse.setTotal(cnt);
        return effectiveCouponRsResponse;
    }

    @Override
    public Boolean deductionOfInventory(String couponId, Date updateTime, Integer deductionAmount) {
        Lock lock = null;
        try {
            lock = redissonClient.getLock(ServicePrefixEnum.SAME_CITY + RedisPrefixKey.COUPON_INVENTORY_CHANGE + couponId);
            boolean b = lock.tryLock(lockTryTime, TimeUnit.SECONDS);
            if (b) {
                //还要判断优惠券是否在有效期内
                Coupon coupon = couponMapper.selectById(couponId);
                if (ObjectUtils.isEmpty(coupon)) {
                    logger.info("优惠券不存在");
                    return false;
                }
                Integer effectiveDays = coupon.getEffectiveDays();
                int inventoryQuantity = coupon.getTotalQuantity() - coupon.getAlreadyRecNum();
                if (inventoryQuantity < deductionAmount) {
                    logger.info("优惠券：" + coupon.getCouponName() + "库存不足");
                    return false;
                }
                if (coupon.getDel()) {
                    logger.info("优惠券：" + coupon.getCouponName() + "已被软删除");
                    return false;
                }
                if (Arrays.asList(CouponStatusEnum.ENDED.getValue(), CouponStatusEnum.LOST_EFFICACY.getValue()).contains(coupon.getStatus())) {
                    logger.info("优惠券：" + coupon.getCouponName() + "已结束/失效");
                    return false;
                }
                if (ObjectUtils.isEmpty(effectiveDays)) {
                    Date effectiveTime = coupon.getEffectiveTime();
                    Date failureTime = coupon.getFailureTime();
                    if (failureTime.before(updateTime)) {
                        logger.info("优惠券：" + coupon.getCouponName() + "已结束");
                        return false;
                    }
                    if (effectiveTime.after(new Date())) {
                        logger.info("优惠券：" + coupon.getCouponName() + "未开始");
                        throw new RuntimeException("优惠券：" + coupon.getCouponName() + "未开始");
                    }
                }
                coupon.setAlreadyRecNum(coupon.getAlreadyRecNum() + deductionAmount);
                coupon.setUpdateTime(updateTime);
                couponMapper.updateById(coupon);

                //下架库存不足的优惠券活动
                couponActivityController.downByStockNotEnough(couponId, coupon.getTotalQuantity() - coupon.getAlreadyRecNum());

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (!ObjectUtils.isEmpty(lock)) {
                // 释放锁
                lock.unlock();
            }

        }
        return true;
    }

    @Override
    public List<Coupon> getNormalByCouponIds(List<String> couponIds) {
        QueryWrapper<Coupon> couponQueryWrapper = new QueryWrapper<>();
        couponQueryWrapper.lambda().and(e -> e.isNotNull(Coupon::getEffectiveDays).or().gt(Coupon::getFailureTime, new Date())).apply("total_quantity - already_rec_num > {0}", 0).notIn(Coupon::getStatus, Arrays.asList(CouponStatusEnum.ENDED.getValue(), CouponStatusEnum.LOST_EFFICACY.getValue()))
                .eq(Coupon::getDel, false).in(Coupon::getId, couponIds);
        return couponMapper.selectList(couponQueryWrapper);
    }

    @Scheduled(cron = "30 * * * * ?")
    public void autoLoadAndUnload() {
        List<Coupon> couponList = couponMapper.selectList(new QueryWrapper<Coupon>().and(wrapper -> wrapper.eq("del", false).eq("status", CouponStatusEnum.NOT_STARTED.getValue())).or(wrapper -> wrapper.eq("status", CouponStatusEnum.UNDER_WAY.getValue())));
        ArrayList<String> couponNoStartIds = new ArrayList<>();
        ArrayList<String> couponLoadingIds = new ArrayList<>();
        for (Coupon coupon : couponList) {
            if (ObjectUtils.isEmpty(coupon.getEffectiveDays())) {
                if (CouponStatusEnum.NOT_STARTED.getValue().equals(coupon.getStatus()) && System.currentTimeMillis() >= coupon.getEffectiveTime().getTime()) {
                    couponNoStartIds.add(coupon.getId());
                }
                if (CouponStatusEnum.UNDER_WAY.getValue().equals(coupon.getStatus()) && System.currentTimeMillis() >= coupon.getFailureTime().getTime()) {
                    couponLoadingIds.add(coupon.getId());
                }
            }
        }
        Coupon couponStart = new Coupon();
        couponStart.setStatus(CouponStatusEnum.UNDER_WAY.getValue());
        if (!CollectionUtils.isEmpty(couponNoStartIds)) {
            couponMapper.update(couponStart, new UpdateWrapper<Coupon>().eq("del", false).in("id", couponNoStartIds));
        }
        Coupon couponEnd = new Coupon();
        couponEnd.setStatus(CouponStatusEnum.ENDED.getValue());
        if (!CollectionUtils.isEmpty(couponLoadingIds)) {
            couponMapper.update(couponEnd, new UpdateWrapper<Coupon>().eq("del", false).in("id", couponLoadingIds));
            //需要下架关联的优惠券活动
            couponActivityController.downByCouponIds(couponLoadingIds);
        }
    }

    private void getQueryWrapper(QueryWrapper<?> couponQueryWrapper, String couponName, Byte status) {
        if (ObjectUtils.isNotEmpty(status) && StringUtils.isNotBlank(couponName)) {
            couponQueryWrapper.eq("status", status).eq("del", DEL).like("coupon_name", couponName);
        }
        if (ObjectUtils.isEmpty(status) && StringUtils.isBlank(couponName)) {
            couponQueryWrapper.eq("del", DEL);
        }
        if (ObjectUtils.isEmpty(status) && StringUtils.isNotBlank(couponName)) {
            couponQueryWrapper.eq("del", DEL).like("coupon_name", couponName);
        }
        if (ObjectUtils.isNotEmpty(status) && StringUtils.isBlank(couponName)) {
            couponQueryWrapper.eq("status", status).eq("del", DEL);
        }
    }

    private void discountCheck(BigDecimal discount) {
        String pattern = "^[0-9]+(.[0-9]{1,1})?$";
        boolean isMatch = discount.toString().matches(pattern);
        if (!isMatch) {
            throw new RuntimeException("仅支持输入正整数或一位小数!");
        }
        if (discount.compareTo(BigDecimal.ZERO) < 0 || discount.compareTo(new BigDecimal("9.9")) > 0) {
            throw new RuntimeException("输入范围:0-9.9");
        }
    }

    private void thresholdCheck(BigDecimal threshold) {
        String pattern = "^[0-9]+(.[0-9]{1,2})?$";
        boolean isMatch = threshold.toString().matches(pattern);
        if (!isMatch) {
            throw new RuntimeException("仅支持输入正整数和两位小数!");
        }
    }

    private void totalQuantityCheck(Integer totalQuantity) {
        String pattern = "^[0-9]*$";
        boolean isMatch = totalQuantity.toString().matches(pattern);
        if (!isMatch) {
            throw new RuntimeException("仅支持输入正整数!");
        }
        if (totalQuantity < 1 || totalQuantity > 10000) {
            throw new RuntimeException("输入范围[1,10000]");
        }
    }
}
